// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

#endregion

#nullable enable

namespace AM.Reporting.Table
{
    internal class TableHelper
    {
        private TableObject sourceTable;
        private TableResult resultTable;

        private enum NowPrinting
        {
            None,
            Row,
            Column
        }

        private NowPrinting nowPrinting;
        private bool rowsPriority;
        private int originalRowIndex;
        private int originalColumnIndex;
        private int printingRowIndex;
        private int printingColumnIndex;
        private List<SpanData> columnSpans;
        private List<SpanData> rowSpans;
        private bool pageBreak;

        private bool AutoSpans => sourceTable.ManualBuildAutoSpans;

        #region Build the Table

        public void PrintRow (int rowIndex)
        {
            originalRowIndex = rowIndex;

            if (nowPrinting == NowPrinting.None)
            {
                // we are at the start. Rows will now have priority over columns.
                rowsPriority = true;
            }

            if (rowsPriority)
            {
                switch (nowPrinting)
                {
                    case NowPrinting.None:
                        printingRowIndex = 0;
                        break;

                    case NowPrinting.Column:
                        printingRowIndex++;
                        break;

                    case NowPrinting.Row:
                        // we have two sequential calls of the PrintRow. But we must print
                        // some columns...
                        break;
                }

                // add new row, do not copy cells: it will be done in the PrintColumn.
                var row = new TableRow();
                row.Assign (sourceTable.Rows[rowIndex]);
                row.PageBreak = pageBreak;
                resultTable.Rows.Add (row);

                columnSpans.Clear();
            }
            else
            {
                if (nowPrinting == NowPrinting.Column)
                {
                    // this is the first row inside a column, reset the index
                    printingRowIndex = 0;
                }
                else
                {
                    // not the first row, increment the index
                    printingRowIndex++;
                }

                TableRow row = null;
                if (resultTable.Rows.Count <= printingRowIndex)
                {
                    // index is outside existing rows. Probably not all rows created yet,
                    // we're at the start. Add new row.
                    row = new TableRow();
                    row.Assign (sourceTable.Rows[rowIndex]);
                    resultTable.Rows.Add (row);
                }
                else
                {
                    // do not create row, use existing one
                    row = resultTable.Rows[printingRowIndex];
                }

                // apply page break
                row.PageBreak = pageBreak;

                // copy cells from the template to the result
                CopyCells (originalColumnIndex, originalRowIndex,
                    printingColumnIndex, printingRowIndex);
            }

            nowPrinting = NowPrinting.Row;
            pageBreak = false;
        }

        public void PrintColumn (int columnIndex)
        {
            originalColumnIndex = columnIndex;

            if (nowPrinting == NowPrinting.None)
            {
                // we are at the start. Columns will now have priority over rows.
                rowsPriority = false;
            }

            if (!rowsPriority)
            {
                switch (nowPrinting)
                {
                    case NowPrinting.None:
                        printingColumnIndex = 0;
                        break;

                    case NowPrinting.Column:
                        // we have two sequential calls of the PrintColumn. But we must print
                        // some rows...
                        break;

                    case NowPrinting.Row:
                        printingColumnIndex++;
                        break;
                }

                // add new column, do not copy cells: it will be done in the PrintRow.
                var column = new TableColumn();
                column.Assign (sourceTable.Columns[columnIndex]);
                column.PageBreak = pageBreak;
                resultTable.Columns.Add (column);

                rowSpans.Clear();
            }
            else
            {
                if (nowPrinting == NowPrinting.Row)
                {
                    // this is the first column inside a row, reset the index
                    printingColumnIndex = 0;
                }
                else
                {
                    // not the first column, increment the index
                    printingColumnIndex++;
                }

                TableColumn column = null;
                if (resultTable.Columns.Count <= printingColumnIndex)
                {
                    // index is outside existing columns. Probably not all columns
                    // created yet, we're at the start. Add new column.
                    column = new TableColumn();
                    column.Assign (sourceTable.Columns[columnIndex]);
                    resultTable.Columns.Add (column);
                }
                else
                {
                    // do not create column, use existing one
                    column = resultTable.Columns[printingColumnIndex];
                }

                // apply page break
                column.PageBreak = pageBreak;

                // copy cells from the template to the result
                CopyCells (originalColumnIndex, originalRowIndex,
                    printingColumnIndex, printingRowIndex);
            }

            nowPrinting = NowPrinting.Column;
            pageBreak = false;
        }

        public void PageBreak()
        {
            pageBreak = true;
        }

        private void CopyCells (int originalColumnIndex, int originalRowIndex,
            int resultColumnIndex, int resultRowIndex)
        {
            var cell = sourceTable[originalColumnIndex, originalRowIndex];
            var cellTo = resultTable.GetCellData (resultColumnIndex, resultRowIndex);
            sourceTable.PrintingCell = cellTo;
            var needData = true;

            if (AutoSpans)
            {
                if (rowsPriority)
                {
                    // We are printing columns inside a row. Check if we need to finish the column cell.
                    if (columnSpans.Count > 0)
                    {
                        var spanData = columnSpans[0];

                        // check if we are printing the last column of the cell's span. From now, we will not accept
                        // the first column.
                        if (originalColumnIndex == spanData.originalCellOrigin.X + spanData.originalCell.ColSpan - 1)
                        {
                            spanData.finishFlag = true;
                        }

                        if ((spanData.finishFlag && originalColumnIndex == spanData.originalCellOrigin.X) ||
                            (originalColumnIndex < spanData.originalCellOrigin.X ||
                             originalColumnIndex > spanData.originalCellOrigin.X + spanData.originalCell.ColSpan - 1))
                        {
                            columnSpans.Clear();
                        }
                        else
                        {
                            spanData.resultCell.ColSpan++;
                            needData = false;
                        }
                    }

                    // add the column cell if it has ColSpan > 1
                    if (cell.ColSpan > 1 && columnSpans.Count == 0)
                    {
                        var spanData = new SpanData();
                        columnSpans.Add (spanData);

                        spanData.originalCell = cell;
                        spanData.resultCell = cellTo;
                        spanData.originalCellOrigin = new Point (originalColumnIndex, originalRowIndex);
                        spanData.resultCellOrigin = new Point (resultColumnIndex, resultRowIndex);
                    }

                    // now check the row cells. Do this once for each row.
                    if (printingColumnIndex == 0)
                    {
                        for (var i = 0; i < rowSpans.Count; i++)
                        {
                            var spanData = rowSpans[i];

                            // check if we are printing the last row of the cell's span. From now, we will not accept
                            // the first row.
                            if (originalRowIndex == spanData.originalCellOrigin.Y + spanData.originalCell.RowSpan - 1)
                            {
                                spanData.finishFlag = true;
                            }

                            if ((spanData.finishFlag && originalRowIndex == spanData.originalCellOrigin.Y) ||
                                (originalRowIndex < spanData.originalCellOrigin.Y ||
                                 originalRowIndex > spanData.originalCellOrigin.Y + spanData.originalCell.RowSpan - 1))
                            {
                                rowSpans.RemoveAt (i);
                                i--;
                            }
                            else
                            {
                                spanData.resultCell.RowSpan++;
                            }
                        }
                    }

                    // check if we should skip current cell because it is inside a span
                    for (var i = 0; i < rowSpans.Count; i++)
                    {
                        var spanData = rowSpans[i];

                        if (resultColumnIndex >= spanData.resultCellOrigin.X &&
                            resultColumnIndex <= spanData.resultCellOrigin.X + spanData.resultCell.ColSpan - 1 &&
                            resultRowIndex >= spanData.resultCellOrigin.Y &&
                            resultRowIndex <= spanData.resultCellOrigin.Y + spanData.resultCell.RowSpan)
                        {
                            needData = false;
                            break;
                        }
                    }

                    // add the row cell if it has RowSpan > 1 and not added yet
                    if (cell.RowSpan > 1 && needData)
                    {
                        var spanData = new SpanData();
                        rowSpans.Add (spanData);

                        spanData.originalCell = cell;
                        spanData.resultCell = cellTo;
                        spanData.originalCellOrigin = new Point (originalColumnIndex, originalRowIndex);
                        spanData.resultCellOrigin = new Point (resultColumnIndex, resultRowIndex);
                    }
                }
                else
                {
                    // We are printing rows inside a column. Check if we need to finish the row cell.
                    if (rowSpans.Count > 0)
                    {
                        var spanData = rowSpans[0];

                        // check if we are printing the last row of the cell's span. From now, we will not accept
                        // the first row.
                        if (originalRowIndex == spanData.originalCellOrigin.Y + spanData.originalCell.RowSpan - 1)
                        {
                            spanData.finishFlag = true;
                        }

                        if ((spanData.finishFlag && originalRowIndex == spanData.originalCellOrigin.Y) ||
                            (originalRowIndex < spanData.originalCellOrigin.Y ||
                             originalRowIndex > spanData.originalCellOrigin.Y + spanData.originalCell.RowSpan - 1))
                        {
                            rowSpans.Clear();
                        }
                        else
                        {
                            spanData.resultCell.RowSpan++;
                            needData = false;
                        }
                    }

                    // add the row cell if it has RowSpan > 1
                    if (cell.RowSpan > 1 && rowSpans.Count == 0)
                    {
                        var spanData = new SpanData();
                        rowSpans.Add (spanData);

                        spanData.originalCell = cell;
                        spanData.resultCell = cellTo;
                        spanData.originalCellOrigin = new Point (originalColumnIndex, originalRowIndex);
                        spanData.resultCellOrigin = new Point (resultColumnIndex, resultRowIndex);
                    }

                    // now check the column cells. Do this once for each column.
                    if (printingRowIndex == 0)
                    {
                        for (var i = 0; i < columnSpans.Count; i++)
                        {
                            var spanData = columnSpans[i];

                            // check if we are printing the last column of the cell's span. From now, we will not accept
                            // the first column.
                            if (originalColumnIndex ==
                                spanData.originalCellOrigin.X + spanData.originalCell.ColSpan - 1)
                            {
                                spanData.finishFlag = true;
                            }

                            if ((spanData.finishFlag && originalColumnIndex == spanData.originalCellOrigin.X) ||
                                (originalColumnIndex < spanData.originalCellOrigin.X ||
                                 originalColumnIndex >
                                 spanData.originalCellOrigin.X + spanData.originalCell.ColSpan - 1))
                            {
                                columnSpans.RemoveAt (i);
                                i--;
                            }
                            else
                            {
                                spanData.resultCell.ColSpan++;
                            }
                        }
                    }

                    // check if we should skip current cell because it is inside a span
                    for (var i = 0; i < columnSpans.Count; i++)
                    {
                        var spanData = columnSpans[i];

                        if (resultColumnIndex >= spanData.resultCellOrigin.X &&
                            resultColumnIndex <= spanData.resultCellOrigin.X + spanData.resultCell.ColSpan - 1 &&
                            resultRowIndex >= spanData.resultCellOrigin.Y &&
                            resultRowIndex <= spanData.resultCellOrigin.Y + spanData.resultCell.RowSpan)
                        {
                            needData = false;
                            break;
                        }
                    }

                    // add the column cell if it has ColSpan > 1 and not added yet
                    if (cell.ColSpan > 1 && needData)
                    {
                        var spanData = new SpanData();
                        columnSpans.Add (spanData);

                        spanData.originalCell = cell;
                        spanData.resultCell = cellTo;
                        spanData.originalCellOrigin = new Point (originalColumnIndex, originalRowIndex);
                        spanData.resultCellOrigin = new Point (resultColumnIndex, resultRowIndex);
                    }
                }
            }
            else
            {
                cellTo.ColSpan = cell.ColSpan;
                cellTo.RowSpan = cell.RowSpan;
            }

            if (needData)
            {
                cell.SaveState();
                cell.GetData();
                cellTo.RunTimeAssign (cell, true);
                cell.RestoreState();
            }
        }

        #endregion

        #region Aggregate Functions

        #endregion

        public TableHelper (TableObject source, TableResult result)
        {
            sourceTable = source;
            resultTable = result;
            columnSpans = new List<SpanData>();
            rowSpans = new List<SpanData>();
        }


        private class SpanData
        {
            public TableCell originalCell;
            public TableCellData resultCell;
            public Point originalCellOrigin;
            public Point resultCellOrigin;
            public bool finishFlag;
        }
    }
}
